/*
 * Gedigarr - Falling block game.
 * Copyright (C) 2010  BoredCorp
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
package gedigarr.grid;

import gedigarr.game.GameConstants;
import gedigarr.objects.*;
import java.awt.Color;
import java.awt.Point;

/**
 * Handles all Grid management and functions. Also includes active Tetromino
 * and its modifier methods.<p>
 * Does not handle timers, input hand
 * 
 * r1 - Initial revision<br>
 * Moved grid and active tetromino functions out of GameState to here.<br>
 * Fixed bug - tail of L/J blocks moved into an active cell.
 * @author JC
 */
public class GameGrid implements GridInterface<Cell> {

  private Cell[][] grid;
  private Tetromino tActive;
  private Tetromino tNext;
  private Point pos = new Point( 0, 0 );
  private Point posGhost = new Point( 0, 0 );
  private Tetromino[] tetros;
  private int rowCount = 0;
  private Color bgColour = Color.BLACK;
  private Color ghostColour = new Color( 40, 40, 40 );

  public GameGrid() {
    fillTetros();
    tActive = randomTetro();
    grid = new Cell[ GameConstants.GRID_HEIGHT ][ GameConstants.GRID_WIDTH ];
    for ( int y = 0; y < GameConstants.GRID_HEIGHT; y++ ) {
      for ( int x = 0; x < GameConstants.GRID_WIDTH; x++ ) {
        grid[y][x] = new Cell( false, false, Color.BLACK, "images/trans.png" );
      //grid[y][x].setActive( Cell.INACTIVE );
      }
    }
/*
    grid[5][3].setChar('I');
    grid[6][4].setChar('♥');
    
    grid[7][2].setChar('J');
    grid[7][3].setChar('e');
    grid[7][4].setChar('s');
    grid[7][5].setChar('s');
*/
    
    //System.out.println(grid[0][0]);
    //grid[GRID_HEIGHT-1][0] = new Cell(true, true, Color.GREEN);
  }

  public void setup() {
    clearGrid();
    //TODO Move somewhere more betterer.
    this.tNext = randomTetro();
    newBlock();
    this.rowCount = 0;
  }

  private void fillTetros() {
    tetros = new Tetromino[ 7 ];
    tetros[0] = new TetroI();
    tetros[1] = new TetroJ();
    tetros[2] = new TetroL();
    tetros[3] = new TetroO();
    tetros[4] = new TetroS();
    tetros[5] = new TetroT();
    tetros[6] = new TetroZ();
  }

  public int getRows() {
    return grid == null ? 0 : grid.length;
  }

  public int getColumns() {
    return getRows() == 0 ? 0 : grid[0] == null ? 0 : grid[0].length;
  }

  public Cell get( int x, int y ) {
    return grid[y][x];
  }

  public int getClearedRowCount() {
    return rowCount;
  }

  public Tetromino getNextTetro() {
    return this.tNext;
  }

  /**
   * Moves a row of the grid down drop rows. Clears old row position.
   * Grid array remains intact, no array is reinitialised.
   * 
   * @param row
   * @param drop
   */
  private void dropRow( int row, int drop ) {
    int newRow = row + drop;

    for ( int i = 0; i < GameConstants.GRID_WIDTH; i++ ) {
      boolean solid = grid[row][i].isSolid();
      boolean visible = grid[row][i].isVisible();
      Color colour = grid[row][i].getBGColour();

      grid[row][i].setSolid( false );
      grid[row][i].setVisible( false );
      grid[row][i].setBGColour( Color.BLACK );

      grid[newRow][i].setSolid( solid );
      grid[newRow][i].setVisible( visible );
      grid[newRow][i].setBGColour( colour );
    }
  }

  public int dropAll() {
    //row del check
    int clear = 0;
    for ( int row = GameConstants.GRID_HEIGHT - 1; row >= 0; row-- ) {
      if ( isComplete( row ) ) {
        clear++;
      } else {
        if ( clear > 0 ) {
          dropRow( row, clear );
        }
      }
    }
    rowCount += clear;
    return clear;
  }

  public int dropActive() {
    int count = 0;
    while ( moveActiveDown() ) {
      count++;
    }
    return count;
  }

  public int countDrop() {
    int count = 0;
    while ( !willHit( tActive, pos.x, pos.y + count + 1 ) ) {
      count++;
    }
    return count;
  }

  public boolean isComplete( int row ) {
    Cell[] y = grid[row];
    for ( int x = 0; x < GameConstants.GRID_WIDTH; x++ ) {
      //if just one is not active, it is not complete
      if ( !y[x].isSolid() ) {
        return false;
      }
    }
    //no falsehoods were found.
    return true;
  }

  public boolean moveActiveLeft() {
    int newIndex = pos.x - 1;//Putil.bindIndex( GRID_WIDTH, last.x - 1 );
    if ( !willHit( tActive, newIndex, pos.y ) ) {
      setLocation( newIndex, pos.y );//pos.x = newIndex;
      return true;
    }
    return false;
  }

  public boolean moveActiveRight() {
    int newIndex = pos.x + 1;//Putil.bindIndex( GRID_WIDTH, last.x + 1 );
    if ( !willHit( tActive, newIndex, pos.y ) ) {
      setLocation( newIndex, pos.y );//pos.x = newIndex;
      return true;
    }
    return false;
  }

  /**
   * Returns true if drop is legal. False if a hit will result.
   * @return
   */
  public boolean moveActiveDown() {
    if ( !willHit( tActive, pos.x, pos.y + 1 ) ) {
      setLocation( pos.x, pos.y + 1 );//pos.y += 1;
      return true;
    }
    return false;
  }

  /**
   * Rotates the active tetro once. Only takes effect if the new position will 
   * not result in a hit.
   */
  public boolean rotate() {
    if ( !willHit( tActive, tActive.getState() + 1, pos.x, pos.y ) ) {
      tActive.rotateRight();
      return true;
    }
    return false;
  }

  /** 
   * All purpose tetromino hit check. Takes in the Tetromino to check, and the 
   * grid x/y co-ords to superimpose it onto.
   * Can be used to check: adding x or y; adding a new Tetromino; 
   * rotating a Tetromino. Before applying to the grid proper.
   * 
   * @param t Tetromino to check against
   * @param xT new X position
   * @param yT new Y position
   * @return true if this imaginary tetro will hit, false if clear.
   */
  public boolean willHit( Tetromino tetro, int frame, int xT, int yT ) {
    //Tetromino tetro = tActive;
    //check for cell overlap
    for ( int yM = 0; yM < tetro.getHeight( frame ); yM++ ) {
      for ( int xM = 0; xM < tetro.getWidth( frame ); xM++ ) {
        if ( tetro.get( frame, xM, yM ).isSolid() ) {
          //localise coords to grid
          int x = xM + xT + tetro.getXPad( frame );// < 0 ? GRID_WIDTH - 1 : (xM + xT) % GRID_WIDTH;
          int y = yM + yT + tetro.getYPad( frame );
          //lower boundary check
          if ( y >= GameConstants.GRID_HEIGHT ) {
            return true;
          }

//wall boundary hit
          if ( x >= GameConstants.GRID_WIDTH ) {
            return true;
          } else if ( x < 0 ) {
            return true;
          }
//if the current active cell is already active, we have a hit.
          if ( grid[y][x].isSolid() ) {
            return true;
          }

        }
      }
    }
    return false;
  }

  public boolean willHit( Tetromino tetro, int xT, int yT ) {
    return willHit( tetro, tetro.getState(), xT, yT );
  }

  public Tetromino randomTetro() {
    return tetros[(int) (Math.random() * tetros.length)];
  }

  public void refreshGhost() {
    setLocation( pos.x, pos.y );
  }

  /**
   * Applies the current active tetromino to the gamegrid, if one exists.
   */
  public void placeActive() {
    if ( tActive != null ) {
      setActive( true, true );
    }
  }

  /**
   * Creates a new (psuedo-)random block in the starting position.
   * <p>
   * If the new block causes game over: returns false; otherwise returns true;
   * @return false for game over, true for resume.
   */
  public boolean newBlock() {
    Tetromino newTetro = randomTetro();
    int xT = (GameConstants.GRID_WIDTH - tNext.getWidth()) / 2;
    int yT = 0;

    tActive.setState( 0 );
    this.tActive = this.tNext;
    setNext( newTetro );

    this.setLocation( xT, yT );
    //setActive(false, true);

    //if the new tetro hits an active cell, game over! but does it belong here?
    if ( willHit( tActive, xT, yT ) ) {
      return false;
    }
    return true;
  }

  private void setNext( Tetromino newTetro ) {
    this.tNext = newTetro;
  }

  private void setLocation( int x, int y ) {
  //  setActive(false, false);
    this.pos.setLocation( x, y );
    this.posGhost.setLocation( x, y + countDrop() );
  //  setActive(false, true);
  }

  public void setVisible( Tetromino t, int x, int y, Color colour, boolean v ) {
    for ( int yM = 0; yM < t.getHeight(); yM++ ) {
      for ( int xM = 0; xM < t.getWidth(); xM++ ) {
        if ( t.get( xM, yM ).isVisible() ) {
          grid[y + yM][x + xM].setBGColour( colour );
          grid[y + yM][x + xM].setVisible( v );
        }
      }
    }
  }

  public void setSolid( Tetromino t, int x, int y, boolean s ) {
    for ( int yM = 0; yM < t.getHeight(); yM++ ) {
      for ( int xM = 0; xM < t.getWidth(); xM++ ) {
        if ( t.get( xM, yM ).isSolid() ) {
          grid[y + yM][x + xM].setSolid( s );
        }
      }
    }
  }

  public void setActive( boolean solid, boolean visible ) {
    for ( int yM = 0; yM < tActive.getHeight(); yM++ ) {
      for ( int xM = 0; xM < tActive.getWidth(); xM++ ) {
        int x = xM + pos.x + tActive.getXPad();
        int y = yM + pos.y + tActive.getYPad();
        int xG = xM + posGhost.x + tActive.getXPad();
        int yG = yM + posGhost.y + tActive.getYPad();
        if ( tActive.get( xM, yM ).isSolid() ) {
          grid[y][x].setSolid( solid );
        }
        if ( tActive.get( xM, yM ).isVisible() ) {
          if ( visible ) {
            grid[yG][xG].setBGColour( ghostColour );
            grid[y][x].setBGColour( tActive.get( xM, yM ).getBGColour() );
          } else {
            grid[yG][xG].setBGColour( bgColour );
            grid[y][x].setBGColour( bgColour );
          }
          grid[yG][xG].setVisible( visible );
          grid[y][x].setVisible( visible );
        }
      }
    }
  }

  public void setGhost( boolean visible ) {
    for ( int yM = 0; yM < tActive.getHeight(); yM++ ) {
      for ( int xM = 0; xM < tActive.getWidth(); xM++ ) {
        int x = (xM + pos.x) + tActive.getXPad(); //< 0 ? GRID_WIDTH - 1 : (xM + pos.x) % GRID_WIDTH + tActive.getXPad();//xM > GRID_WIDTH ? yM + last.x : ;
        int y = yM + pos.y + tActive.getYPad() + countDrop();
        if ( tActive.get( xM, yM ).isVisible() ) {
          if ( visible ) {
            grid[y][x].setBGColour( tActive.get( xM, yM ).getBGColour() );
          } else {
            grid[y][x].setBGColour( Color.BLACK );
          }
          grid[y][x].setVisible( visible );
        }
      }
    }
  }

  public void clearGrid() {
    for ( int yD = 0; yD < GameConstants.GRID_HEIGHT; yD++ ) {
      for ( int xD = 0; xD < GameConstants.GRID_WIDTH; xD++ ) {
        grid[yD][xD].setSolid( false );
        grid[yD][xD].setVisible( false );
        grid[yD][xD].setBGColour( Color.BLACK );
      }
    }
  }
}
